package com.haibara.aiojcodesandbox.core;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.haibara.aiojcodesandbox.model.*;
import com.haibara.aiojcodesandbox.model.enums.JudgeInfoMessageEnum;
import com.haibara.aiojcodesandbox.utils.ProcessUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 代码沙箱模板
 * 注意每个实现类必须自定义代码存放路径，参考{@link JavaNativeCodeSandbox}
 *
 * @author 程崎
 * @since 2023/09/01
 */
@Slf4j
public abstract class CodeSandboxTemplate implements CodeSandbox {

    String prefix;

    String globalCodeDirPath;

    String globalCodeFileName;

    /**
     * 超时时间，超过10秒则结束
     */
    public static final Long DEFAULT_TIME_OUT = 10000L;


    /**
     * 每个实现类必须实现编译以及运行的cmd
     *
     * @param userCodeParentPath 代码所在的父目录
     * @param userCodePath       代码所在目录
     * @return {@link CodeSandboxCmd}
     */
    abstract CodeSandboxCmd getCmd(String userCodeParentPath, String userCodePath,Long questionId);

    /**
     * 保存代码到文件中，注意这里需要实现，不同编程语言要放到不同文件夹中
     * 保存到文件中的格式应为: UUID/代码文件，后面删除代码文件需要将代码文件的父文件删除
     *
     * @param code 代码
     * @return {@link File}
     */
    public File saveCodeToFile(String code, Long questionId) {
        String globalCodePath = System.getProperty("user.dir") + globalCodeDirPath;
        System.out.println("globalCodePath"+globalCodePath);
        if (!FileUtil.exist(globalCodePath)) {
            FileUtil.mkdir(globalCodePath);
        }

        // 存放用户代码
        String userCodeParentPath = globalCodePath + prefix + File.separator + UUID.randomUUID();
        String userCodePath = userCodeParentPath + globalCodeFileName+"question_"+questionId+".java";
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    /**
     * 编译代码，注意编译代码要返回编译的信息
     *
     * @param compileCmd 编译命令
     * @return {@link ExecuteMessage}
     * @throws IOException IOException
     */
    private ExecuteMessage compileCode(String compileCmd) throws IOException {
        Process compileProcess = Runtime.getRuntime().exec(compileCmd);
        return ProcessUtil.handleProcessMessage(compileProcess, "编译");
    }

    public List<ExecuteMessage> runFile(File userCodeFile, List<String> inputList, Long questionId) {
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();

        int size = inputList.size();
        // 将executeMessageList初始化，每个元素设置为null
        List<ExecuteMessage> executeMessageList = new ArrayList<>(Collections.nCopies(size, null));
        ExecutorService executorService = Executors.newFixedThreadPool(size);
        for (int i = 0; i < size; i++) {
            String inputArgs = inputList.get(i);
            String codeFileName = "question_"+questionId;
            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s %s %s", userCodeParentPath, codeFileName,inputArgs);
            try {
                // 多线程执行代码，获取结果
                System.out.println(runCmd);
                int finalI = i;
                executorService.submit(() -> {
                    runCode runCode = new runCode(runCmd, executeMessageList, finalI);
                    runCode.run(); // 注意这里使用run方法直接执行
                });
            } catch (Exception e) {
                throw new RuntimeException("执行错误", e);
            }
        }
        executorService.shutdown(); // 启动顺序关闭，执行以前提交的任务，但不接受新任务
        try {
            if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                executorService.shutdownNow(); // 取消当前执行的任务
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt(); // 重新设置中断状态
        }
        System.out.println(executeMessageList);
        return executeMessageList;
    }


    @Override
    public final ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        List<String> inputList = executeCodeRequest.getInputList();
        String code = executeCodeRequest.getCode();
        Long questionId = executeCodeRequest.getQuestionId();
        // 保存代码
        File userCodeFile = saveCodeToFile(code,questionId);
        String userCodePath = userCodeFile.getAbsolutePath();
        String userCodeParentPath = userCodeFile.getParentFile().getAbsolutePath();
        CodeSandboxCmd cmdFromLanguage = getCmd(userCodeParentPath, userCodePath,questionId);
        String compileCmd = cmdFromLanguage.getCompileCmd();
        String runCmd = cmdFromLanguage.getRunCmd();
        // 编译代码
        try {
            ExecuteMessage executeMessage = compileCode(compileCmd);
            if (executeMessage.getExitCode() != 0) {
                FileUtil.del(userCodeParentPath);
                // 返回编译错误信息
                return ExecuteCodeResponse
                        .builder()
                        .status(ExecuteCodeResponse.ERROR)
                        .message(JudgeInfoMessageEnum.COMPILE_ERROR.getValue())
                        .build();
            }
        } catch (IOException e) {
            FileUtil.del(userCodeParentPath);
            return errorResponse(e);
        }

        // 执行代码
        try {
            List<ExecuteMessage> executeMessageList = runFile(userCodeFile,inputList,questionId);
            // 返回处理结果
            ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
            executeCodeResponse.setStatus(ExecuteCodeResponse.SUCCESS);
            JudgeInfo judgeInfo = new JudgeInfo();
            executeCodeResponse.setJudgeInfo(judgeInfo);
            List<String> outputList = new LinkedList<>();
            long maxTime = 0;
            double maxMemory = 0;

            for (ExecuteMessage executeMessage : executeMessageList) {
                if (ObjectUtil.equal(0, executeMessage.getExitCode())) {
                    outputList.add(executeMessage.getMessage());
                } else {
                    setErrorMessage(executeCodeResponse, executeMessage.getErrorMessage());
                    executeCodeResponse.setStatus(ExecuteCodeResponse.ERROR);
                    break;
                }
                maxMemory = Math.max(maxMemory,executeMessage.getMemory());
                maxTime = Math.max(maxTime, executeMessage.getTime());
            }
            judgeInfo.setTime(maxTime);
            judgeInfo.setMemory(maxMemory);
            executeCodeResponse.setOutputList(outputList);
            FileUtil.del(userCodeParentPath);
            return executeCodeResponse;
        } catch (RuntimeException e) {
            FileUtil.del(userCodeParentPath);
            return errorResponse(e);
        }
    }

    /**
     * 设置错误信息
     * @param executeCodeResponse
     * @param errorMessage
     */
    private void setErrorMessage(ExecuteCodeResponse executeCodeResponse, String errorMessage){
        if(errorMessage.contains("OutOfMemoryError")){
            executeCodeResponse.setMessage(JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED.getValue());
        }else if(errorMessage.contains("StackOverflowError")){
            executeCodeResponse.setMessage(JudgeInfoMessageEnum.STACK_OVER_FLOW.getValue());
        }else if(errorMessage.contains("Time")){
            executeCodeResponse.setMessage(JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getValue());
        }
    }

    final ExecuteCodeResponse errorResponse(Throwable e) {
        return ExecuteCodeResponse
                .builder()
                .outputList(new ArrayList<>())
                .message(e.getMessage())
                .judgeInfo(new JudgeInfo())
                .status(ExecuteCodeResponse.ERROR)
                .build();
    }
}

class JudgeTimeLimit extends Thread{
    private Process runProcess;
    private List<ExecuteMessage> executeMessageList;

    private boolean flag =true;

    public JudgeTimeLimit(Process runProcess, List<ExecuteMessage> executeMessageList) {
        this.runProcess = runProcess;
        this.executeMessageList = executeMessageList;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                Thread.sleep(CodeSandboxTemplate.DEFAULT_TIME_OUT/10);
                if(!flag){
                    System.out.println("未超时，安全退出");
                    return;
                }
            }
            System.out.println("超时了，中断");
            runProcess.destroy();
            executeMessageList.add(new ExecuteMessage(ExecuteMessage.ERROR,null,JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getValue(), 0,0));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

class runCode extends Thread{
    private String runCmd;
    private List<ExecuteMessage> executeMessageList;
    private int index;

    public runCode(String runCmd, List<ExecuteMessage> executeMessageList, int index) {
        this.runCmd = runCmd;
        this.executeMessageList = executeMessageList;
        this.index = index;
    }

    @Override
    public void run(){
        System.out.println("开始执行代码，线程名称为:"+Thread.currentThread().getName());
        System.out.println(runCmd);
        Process runProcess = null;
        try {
            runProcess = Runtime.getRuntime().exec(runCmd);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 超时控制
        JudgeTimeLimit judgeTimeLimit = new JudgeTimeLimit(runProcess, executeMessageList);
        judgeTimeLimit.start();
        // 执行代码
        ExecuteMessage executeMessage = ProcessUtil.runProcessAndGetMessage(runProcess, "运行");
        System.out.println(executeMessage);
        // 直接将结果设置到测试用例对应的index中，防止其混乱
        executeMessageList.set(index,executeMessage);
        // 让判断超时的线程停止
        judgeTimeLimit.setFlag(false);
    }
}

